#pragma once

#include "Lexer.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <format>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <utility>
#include <variant>
#include <vector>

namespace ztl{
    inline void interpreter(const std::vector<Token> &tokens){
        using ObjectType = std::variant<int64_t>;
        std::map<std::string, ObjectType> objects;
        const auto getValueOrLiteral = [&tokens,&objects](size_t i){
            ObjectType a;
            if(tokens[i].type==TokenType::Literal){
                a=atoi(tokens[i].str.data());
            }else if(tokens[i].type==TokenType::Identifier){
                if(auto it = objects.find(tokens[i].str);it!=objects.end()){
                    a=(*it).second;
                }else{
                    throw std::runtime_error(std::format("error in getting value named '{}'",tokens[i].str));
                }
            }else{
                throw std::runtime_error("error in getting expression result");
            }
            return a;
        };
        const auto getExprAns = [&getValueOrLiteral,&objects,&tokens](size_t i)->std::pair<ObjectType, size_t>{
            ObjectType a=getValueOrLiteral(i);
            ObjectType b;
            if(i+1<tokens.size()&&tokens[i+1].type==TokenType::Operator){
                if(tokens[i+1].type==TokenType::Operator){
                    if(i+2<tokens.size()){
                        b=getValueOrLiteral(i+2);
                    }else{
                        throw std::runtime_error(std::format("error getting a op b, b is not found. a named '{}'",tokens[i].str));
                    }
                }else{
                    throw std::runtime_error(std::format("unknown operator '{}'",tokens[i+1].str));
                }
            }else{
                return {a,1};
            }
            ObjectType res;
            if(std::holds_alternative<int64_t>(a) && std::holds_alternative<int64_t>(b)){
                if(tokens[i+1].str=="+"){
                    res = std::get<int64_t>(a) + std::get<int64_t>(b);
                }else if(tokens[i+1].str=="-"){
                    res = std::get<int64_t>(a) - std::get<int64_t>(b);
                }
                else{
                    throw std::runtime_error(std::format("unknown operator '{}' ",tokens[i+1].str));
                }
            }else{
                throw std::runtime_error(std::format("unknown value type '{}' ,'{}'",tokens[i].str,tokens[i+2].str));
            }
            
            return {res,3};
        };
        for(size_t i=0;i<tokens.size();i++){
            const auto eat = [&i,&tokens](const std::string &s){
                if(i+1<tokens.size()&&tokens[i+1].str==s){
                    i++;
                }else{
                    throw std::runtime_error(std::format("cannot get '{}' after '{}'",s,tokens[i].str));
                }
            };
            if(tokens[i].type==TokenType::Keyword){
                if(tokens[i].str=="int"){
                    if( i+2<tokens.size()
                        &&tokens[i+1].type==TokenType::Identifier
                        && tokens[i+2].str=="="
                    ){
                        std::string valueName = tokens[i+1].str;
                        i+=2;
                        auto res = getExprAns(i+1);
                        objects[valueName] = res.first;
                        i+=res.second;
                    }else{
                        throw std::runtime_error(std::format("error when creatting int value '{}'",tokens[i].str));
                    }
                }
                else if(tokens[i].str=="print"){
                    if(i+1<tokens.size()&&tokens[i+1].str=="("){
                        i++;
                    }else{
                        throw std::runtime_error("cannot get '(' when printing value");
                    }
                    if(!(i+1<tokens.size()))throw std::runtime_error("cannot get value be printed when printing value");
                    auto [res,addi] = getExprAns(i+1);
                    i+=addi;
                    if(i+1<tokens.size()&&tokens[i+1].str==")"){
                        i++;
                    }else{
                        throw std::runtime_error("cannot get ')' when printing value");
                    }
                    std::visit([](auto &res){
                        std::cout<<res<<'\n';
                    },res);
                }
                else{
                    throw std::runtime_error(std::format("interpreter error Cannot find Keyword '{}'",tokens[i].str));
                }
                
            }else if(tokens[i].type==TokenType::Identifier){
                if( i+1<tokens.size()
                    && tokens[i+1].str=="="
                ){
                    std::string valueName = tokens[i].str;
                    i+=1;
                    auto res = getExprAns(i+1);
                    if(auto it = objects.find(valueName);it!=objects.end()){
                        if(it->second.index()!=res.first.index()){
                            throw std::runtime_error(std::format("cannot give value to {}, because type is not same",valueName));
                        }
                    }else{
                        throw std::runtime_error(std::format("error when getting int value named '{}'",valueName));
                    }
                    objects[valueName] = res.first;
                    i+=res.second;
                }else{
                    throw std::runtime_error(std::format("error when creatting int value '{}'",tokens[i].str));
                }
            }
            else{
                throw std::runtime_error(std::format("interpreter error at '{}'",tokens[i].str));
            }
            eat(";");
        }
    }
}